﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Resources;
using System.Xml.Linq;
using System.Net;

using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using System.Security;
using System.IO;
using System.Collections;
using System.Threading;
using System.Globalization;

using King.Reflection;
using King.Declarative;
using King.Extensions;
using King.Text;
using King.Text.Xml;
using King.Collections;
using King.Rest;
using King.Atom;

namespace King.Azure.Rest {

    public enum AzureStorageKeyType {
        Primary,
        Secondary
    }
    public enum AzureLogResult {
        Success,
        Failed,
        InProgress
    }
    public enum AzureContainerAccess {
        /// <summary>
        /// Specifies full public read access for container and blob data. 
        /// Clients can enumerate blobs within the container via anonymous 
        /// request, but cannot enumerate containers within the storage account.
        /// </summary>
        Container,

        /// <summary>
        /// Specifies public read access for blobs. Blob data within this 
        /// container can be read via anonymous request, but container data 
        /// is not available. Clients cannot enumerate blobs within the 
        /// container via anonymous request.
        /// </summary>
        Blob,
    }
    public enum AzureBlobType {
        Block,
        Page
    }

    // shared classes
    public sealed class AzureIfChanged {
        private string m_etag;
        private DateTime? m_timestamp;

        public AzureIfChanged(string etag) {
            m_etag = etag;
        }
        public AzureIfChanged(DateTime timestamp) {
            m_timestamp = timestamp;
        }

        [RestHeader("if-none-match")]
        public string Etag {
            get { return m_etag; }
        }
        [RestHeader("if-modified-since")]
        public DateTime? Timestamp {
            get { return m_timestamp; }
        }

        public override string ToString() {
            return m_etag ?? m_timestamp.ToString();
        }
    }
    public sealed class AzureIfUnchanged {
        public static implicit operator AzureIfUnchanged(string etag) {
            return new AzureIfUnchanged(etag);
        }
        public static implicit operator AzureIfUnchanged(DateTime timestamp) {
            return new AzureIfUnchanged(timestamp);
        }

        private string m_etag;
        private DateTime? m_timestamp;

        public AzureIfUnchanged() {
        }
        public AzureIfUnchanged(string etag) {
            m_etag = etag;
        }
        public AzureIfUnchanged(DateTime timestamp) {
            m_timestamp = timestamp;
        }

        [RestHeader("if-match")]
        private string Force {
            get {
                if (m_etag == null && m_timestamp == null)
                    return "*";
                return null;
            }
        }

        [RestHeader("if-match")]
        public string Etag {
            get { return m_etag; }
        }
        [RestHeader("if-unmodified-since")]
        public DateTime? Timestamp {
            get { return m_timestamp; }
        }

        public override string ToString() {
            return m_etag ?? m_timestamp.ToString();
        }
    }

    public abstract class AzureStorageManagement : AzureRestApi {
        protected const string ContentType = "text/plain; charset=UTF-8";
        protected const string AuthorizationHeader = "Authorization";
        protected const string SharedKey = "SharedKey";

        [RestHeader(AzureFormat.HeaderFormat)]
        private const string Version = "2011-08-18";

        private string m_accountName;
        private byte[] m_key;

        internal AzureStorageManagement(string accountName, string key)
            : this(accountName, Convert.FromBase64String(key)) {
        }
        internal AzureStorageManagement(string accountName, byte[] key) {
            m_accountName = accountName;
            m_key = key;
        }

        private string GetAuthorizationSignature(HttpWebRequest request, bool tableScheme) {

            var headers = string.Join("\n", tableScheme ? 
                new string[] {
                    request.Method,
                    request.Headers[HttpRequestHeader.ContentMd5],
                    request.Headers[HttpRequestHeader.ContentType],
                    request.Headers[HttpRequestHeader.Date],

                } : new string[] {
                    request.Method,
                    request.Headers[HttpRequestHeader.ContentEncoding],
                    request.Headers[HttpRequestHeader.ContentLanguage],
                    request.ContentLength < 0 ? null : 
                        request.ContentLength.ToString(),
                    request.Headers[HttpRequestHeader.ContentMd5],
                    request.Headers[HttpRequestHeader.ContentType],
                    request.Headers[HttpRequestHeader.Date],
                    request.Headers[HttpRequestHeader.IfModifiedSince],
                    request.Headers[HttpRequestHeader.IfMatch],
                    request.Headers[HttpRequestHeader.IfNoneMatch],
                    request.Headers[HttpRequestHeader.IfUnmodifiedSince],
                    /* range */ null,
                }
            );

            var canonicalizedHeader = tableScheme ? null :
                from o in request.Headers.AllKeys
                let name = o.ToLowerInvariant()
                where name.StartsWith("x-ms")
                orderby name
                // "Unfold the string by replacing any breaking white space with a single space"
                let value = Regex.Replace(request.Headers[o], @"[\r\n]+", " ")
                select name + ":" + value;

            var canonicalizedResource = (tableScheme ?
                new TextJoin(prefix: '/') {
                    // path
                    m_accountName,
                    request.RequestUri.Segments,

                    // comp parameter
                    from nameValue in request.RequestUri.Queries()
                    let name = nameValue.Item1
                    let value = nameValue.Item2
                    where name.CaseInsensitveEquals("comp")
                    select new TextJoin('=', prefix: '?') { name, value } 
                } :
                
                new TextJoin('\n') {
                    // path
                    new TextJoin(prefix: '/') {
                        m_accountName,
                        request.RequestUri.Segments
                    },

                    // query
                    from nameValue in request.RequestUri.Queries()
                    let name = Uri.UnescapeDataString(nameValue.Item1).ToLowerInvariant()
                    let value = Uri.UnescapeDataString(nameValue.Item2)
                    orderby name
                    select name + ":" + value
                }
            ).ReadToEnd();

            // authentication string
            var signature = new TextJoin('\n') {
                headers,
                canonicalizedHeader,
                canonicalizedResource
            }.ReadToEnd();

            return signature;
        }
        protected void AddAuthroizationHeader(HttpWebRequest request, bool tableScheme = false) {
            var result =
                new TextJoin(" ") {
                    SharedKey, new TextJoin(":") { 
                        m_accountName,  

                        // signature -> utf8 -> crypto hash -> base64 string
                        Convert.ToBase64String(
                            new HMACSHA256(m_key).ComputeHash(
                                Encoding.UTF8.GetBytes(
                                    GetAuthorizationSignature(request, tableScheme)
                                )
                            )
                        ) 
                    }
                }.ReadToEnd();

            request.Headers.Add(AuthorizationHeader, result);
        }

        public string AcccountName {
            get { return m_accountName; }
        }

        [RestHeader]
        public DateTime Date {
            get { return DateTime.UtcNow; }
        }

        public byte[] Key {
            get { return m_key; }
        }
    }

    // shared logging
    internal sealed class AzureServicePropertiesTemplate : TextDeclaration {

        [RestHeader]
        internal const string ContentType = "application/xml; charset=UTF-8";

        private static readonly XElement s_xmlTemplate;

        static AzureServicePropertiesTemplate() {
            var assembly = Assembly.GetExecutingAssembly();
            using (var stream = new StreamReader(assembly.GetManifestResourceStream("az.ServiceProperties.xml"))) {
                var text = stream.ReadToEnd();
                var xml = XElement.Parse(text);

                s_xmlTemplate = xml;

                xml = new XElement(s_xmlTemplate);
                var element = xml.GetElement("//Metrics/Version");
                element.Value = "true";
                text = xml.ToString();
            }
        }

        private IEnumerable<RestArgument> m_arguments;

        internal AzureServicePropertiesTemplate(IEnumerable<RestArgument> arguments) {
            m_arguments = arguments;
        }

        protected override IEnumerable Children(DeclarationContext frame) {
            var xml = new XElement(s_xmlTemplate);

            foreach (var argument in m_arguments) {
                var target = xml.GetElement(argument.Name);
                target.Value = argument.Value.ToString();
            }

            return new TextSingleSpaced {
                "<?xml version=\"1.0\" encoding=\"utf-8\"?>", 
                xml.ToString()
            };
        }
    }
    public abstract class AzureLogging {

        private int? m_logVersion = null;
        private bool? m_logDelete = null;
        private bool? m_logRead = null;
        private bool? m_logWrite = null;
        private bool? m_logRetentionEnabled = null;
        private int? m_logRetentionDays = null;

        internal AzureLogging(
            int? version = null,
            bool? delete = null,
            bool? read = null,
            bool? write = null,
            bool? retentionEnable = null,
            int? retentionDays = null) {

            m_logVersion = version;
            m_logDelete = delete;
            m_logRead = read;
            m_logWrite = write;
            m_logRetentionEnabled = retentionEnable;
            m_logRetentionDays = retentionDays;
        }

        [RestContent("//Logging/Version")]
        public int? Version {
            get { return m_logVersion; }
        }

        [RestContent("//Logging/Delete")]
        public bool? Delete {
            get { return m_logDelete; }
        }

        [RestContent("//Logging/Read")]
        public bool? Read {
            get { return m_logRead; }
        }

        [RestContent("//Logging/Write")]
        public bool? Write {
            get { return m_logWrite; }
        }

        [RestContent("//Logging/RetentionPolicy/Enabled")]
        public bool? RetentionEnabled {
            get { return m_logRetentionEnabled; }
        }

        [RestContent("//Logging/RetentionPolicy/Days")]
        public int? RetentionDays {
            get { return m_logRetentionDays; }
        }
    }
    public abstract class AzureMetrics {

        private int? m_metricsVersion = null;
        private bool? m_metricsEnabled = null;
        private bool? m_metricsIncludeApi = null;
        private bool? m_metricsRetentionEnabled = null;
        private int? m_metricsRetentionDays = null;

        internal AzureMetrics(
            int? version = null,
            bool? enable = null,
            bool? includeApi = null,
            bool? retentionEnable = null,
            int? retentionDays = null) {

            m_metricsVersion = version;
            m_metricsEnabled = enable;
            m_metricsIncludeApi = includeApi;
            m_metricsRetentionEnabled = retentionEnable;
            m_metricsRetentionDays = retentionDays;
        }

        [RestContent("//Metrics/Version")]
        public int? Version {
            get { return m_metricsVersion; }
        }

        [RestContent("//Metrics/Enabled")]
        public bool? Enabled {
            get { return m_metricsEnabled; }
        }

        [RestContent("//Metrics/IncludeAPIs")]
        public bool? IncludeApi {
            get { return m_metricsIncludeApi; }
        }

        [RestContent("//Metrics/RetentionPolicy/Enabled")]
        public bool? RetentionEnabled {
            get { return m_metricsRetentionEnabled; }
        }

        [RestContent("//Metrics/RetentionPolicy/Days")]
        public int? RetentionDays {
            get { return m_metricsRetentionDays; }
        }
    }

    // blob.core.windows.net
    [RestHost("blob")]
    public sealed class AzureBlobManagment : AzureStorageManagement {

        public AzureBlobManagment(string accountName, string key)
            : base(accountName, key) {
        }
        public AzureBlobManagment(string accountName, byte[] key)
            : base(accountName, key) {
        }

        internal HttpWebResponse Invoke(params object[] arguments) {
            return InvokeWithContext(this, arguments);
        }
        
        internal HttpWebResponse InvokeWithContext(object context, params object[] arguments) {
            object body;
            var request = ConstructRequest(context, arguments, out body);
            AddAuthroizationHeader(request);
            return (HttpWebResponse)request.GetResponse();
        }
        
        [RestHost]
        public new string AcccountName {
            get { return base.AcccountName; }
        }

        [RestGet, RestQuery("comp", "list")]
        public HttpWebResponse Containers(
            [RestQuery]string prefix = null,
            [RestQuery]string marker = null,
            [RestQuery]int? maxResults = null,
            [RestQuery(null, "metadata")]bool? include = null) {
            return Invoke(prefix, marker, maxResults, include);
        }

        [RestGet, RestQuery("restype", "service"), RestQuery("comp", "properties")]
        public HttpWebResponse Properties() {
            return Invoke();
        }

        [RestPut, RestQuery("restype", "service"), RestQuery("comp", "properties")]
        public HttpWebResponse Update(
            AzureBlobLogging logging = null,
            AzureBlobMetrics metrics = null) {

            return Invoke(logging, metrics);
        }

        [RestPut]
        public HttpWebResponse CreateContainer(
            string name,
            [RestHeader]AzureContainerAccess? blobPublicAccess = null) {
            return Invoke(name, blobPublicAccess);
        }
        
        public AzureContainer GetContainer(string name) {
            return new AzureContainer(this, name);
        }
    }
    public sealed class AzureContainer {
        private AzureBlobManagment m_credentials;
        private string m_name;

        internal AzureContainer(AzureBlobManagment credentials, string name) {
            m_credentials = credentials;
            m_name = name;
        }
        
        private HttpWebResponse Invoke(params object[] arguments) {
            return m_credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureBlobManagment Credentials {
            get { return m_credentials; }
        }

        [RestPath]
        public string Name {
            get { return m_name; }
        }

        [RestGet, RestQuery("restype", "container"), RestQuery("comp", "list")]
        public HttpWebResponse Blobs(
            [RestQuery]string prefix = null,
            [RestQuery]string delimiter = null,
            [RestQuery]string marker = null,
            [RestQuery]int? maxResults = null,
            [RestQuery(null, "metadata")]bool? include = null) {
            return Invoke(prefix, delimiter, marker, maxResults, include);
        }

        public AzureBlob GetBlob(string name) {
            return new AzureBlob(this, name);
        }

        [RestGet, RestQuery("restype", "container")]
        public HttpWebResponse Properties(
            [RestHeader]AzureContainerAccess? blobPublicAccess = null) {
            return Invoke(blobPublicAccess);
        }

        [RestGet, RestQuery("restype", "container"), RestQuery("comp", "metadata")]
        public HttpWebResponse Metadata() {
            return Invoke();
        }

        [RestGet, RestQuery("restype", "container"), RestQuery("comp", "acl")]
        public HttpWebResponse Acl() {
            return Invoke();
        }

        [RestDelete, RestQuery("restype", "container")]
        public HttpWebResponse Delete() {
            return Invoke();
        }
    }
    public sealed class AzureBlob {
        private AzureContainer m_container;
        private string m_name;

        internal AzureBlob(AzureContainer container, string name) {
            m_container = container;
            m_name = name;
        }
        
        private HttpWebResponse Invoke(params object[] arguments) {
            return m_container.Credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureContainer Container {
            get { return m_container; }
        }

        [RestPath]
        public string Name {
            get { return m_name; }
        }

        [RestPut]
        public HttpWebResponse CreateOrUpdate(
            Stream content,
            int contentLength,
            [RestHeader]string contentType = null,
            [RestHeader]string contentLanguage = null,
            [RestHeader]string cacheControl = null,
            [RestHeader(AzureFormat.HeaderFormat)]int? leaseId = null,
            [RestHeader(AzureFormat.HeaderFormat)]string blobType = "BlockBlob") {

            return Invoke(content, contentLength,
                contentType, contentLanguage, cacheControl, leaseId, blobType);
        }

        [RestHead]
        public HttpWebResponse Properties(AzureIfChanged ifChanged = null) {
            return Invoke(ifChanged);
        }

        [RestGet]
        public HttpWebResponse Get() {
            return Invoke();
        }

        [RestDelete]
        public HttpWebResponse Delete() {
            return Invoke();
        }
    }
    public sealed class AzureBlobLogging : AzureLogging {

        public AzureBlobLogging(
            int? version = null,
            bool? delete = null,
            bool? read = null,
            bool? write = null,
            bool? retentionEnable = null,
            int? retentionDays = null)
            : base(version, delete, read, write,
                retentionEnable, retentionDays) {
        }
    }
    public sealed class AzureBlobMetrics : AzureMetrics {

        public AzureBlobMetrics(
            int? version = null,
            bool? enable = null,
            bool? includeApi = null,
            bool? retentionEnable = null,
            int? retentionDays = null)
            : base(version, enable, includeApi,
                retentionEnable, retentionDays) {
        }
    }

    // queue.core.windows.net
    [RestHost("queue")]
    public sealed class AzureQueueManagment : AzureStorageManagement {
        
        public AzureQueueManagment(string accountName, string key)
            : base(accountName, key) {
        }
        public AzureQueueManagment(string accountName, byte[] key)
            : base(accountName, key) {
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return InvokeWithContext(this, arguments);
        }

        internal HttpWebResponse InvokeWithContext(object context, params object[] arguments) {
            object body;
            var request = ConstructRequest(context, arguments, out body);
            AddAuthroizationHeader(request);
            return (HttpWebResponse)request.GetResponse();
        }

        [RestHost]
        public new string AcccountName {
            get { return base.AcccountName; }
        }

        [RestGet, RestQuery("comp", "list")]
        public HttpWebResponse Queues() {
            return Invoke();
        }

        [RestPut]
        public HttpWebResponse CreateQueue([RestPath]string name) {
            return Invoke(name);
        }

        public AzureQueue GetQueue(string name) {
            return new AzureQueue(this, name);
        }
    }
    public sealed class AzureQueue {
        private AzureQueueManagment m_credentials;
        private string m_name;

        internal AzureQueue(AzureQueueManagment credentials, string name) {
            m_credentials = credentials;
            m_name = name;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return m_credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureQueueManagment Credentials {
            get { return m_credentials; }
        }

        [RestPath]
        public string Name {
            get { return m_name; }
        }

        [RestDelete]
        public HttpWebResponse Delete() {
            return Invoke();
        }

        [RestGet, RestQuery("comp", "metadata")]
        public HttpWebResponse Metadata() {
            return Invoke();
        }

        [RestDelete, RestPath("messages")]
        public HttpWebResponse Clear() {
            return Invoke();
        }

        [RestPost, RestPath("messages")]
        public HttpWebResponse Put(
            Stream message,
            int contentLength,
            [RestQuery]int? visibilityTimeout = null,
            [RestQuery]int? timeToLive = null) {
            return Invoke(message, contentLength, visibilityTimeout, timeToLive);
        }

        [RestGet, RestPath("messages")]
        public HttpWebResponse Get(
            [RestQuery]int? numOfMessages = null,
            [RestQuery]int? visibilityTimeout = null) {
            return Invoke(numOfMessages, visibilityTimeout);
        }

        [RestGet, RestPath("messages"), RestQuery("peekonly", "true")]
        public HttpWebResponse Peek(
            [RestQuery]int? numOfMessages = null) {
            return Invoke(numOfMessages);
        }

        public AzureMessage GetMessage(string name, string popReceipt) {
            return new AzureMessage(this, name, popReceipt);
        }
    }
    [RestPath("messages")]
    public sealed class AzureMessage {
        private AzureQueue m_queue;
        private string m_name;
        private string m_popReceipt;

        internal AzureMessage(AzureQueue queue, string name, string popReceipt) {
            m_queue = queue;
            m_name = name;
            m_popReceipt = popReceipt;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return m_queue.Credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureQueue Container {
            get { return m_queue; }
        }

        [RestPath]
        public string Name {
            get { return m_name; }
        }

        [RestQuery]
        public string PopReceipt {
            get { return m_popReceipt; }
        }

        [RestPut]
        public HttpWebResponse Update(
            Stream message,
            int contentLength,
            [RestQuery]int? visibilityTimeout = null) {
            return Invoke(message, contentLength, visibilityTimeout);
        }

        [RestDelete]
        public HttpWebResponse Delete() {
            return Invoke();
        }
    }
    public sealed class AzureQueueLogging : AzureLogging {

        public AzureQueueLogging(
            int? version = null,
            bool? delete = null,
            bool? read = null,
            bool? write = null,
            bool? retentionEnable = null,
            int? retentionDays = null)
            : base(version, delete, read, write,
                retentionEnable, retentionDays) {
        }
    }
    public sealed class AzureQueueMetrics : AzureMetrics {

        public AzureQueueMetrics(
            int? version = null,
            bool? enable = null,
            bool? includeApi = null,
            bool? retentionEnable = null,
            int? retentionDays = null)
            : base(version, enable, includeApi,
                retentionEnable, retentionDays) {
        }
    }

    // table.core.windows.net
    [RestHost("table"), RestTemplate(typeof(Template))]
    public sealed class AzureTableManagment : AzureStorageManagement {

        [RestHeader]
        internal const string DataServiceVersion = "2.0;NetFx";
        [RestHeader]
        internal const string MaxDataServiceVersion = "2.0;NetFx";

        private class Template : TextDeclaration {

            [RestHeader]
            internal const string ContentType = "application/atom+xml";

            private IEnumerable<RestArgument> m_arguments;

            internal Template(IEnumerable<RestArgument> arguments) {
                m_arguments = arguments;
            }

            protected override IEnumerable Children(DeclarationContext frame) {
                return new AtomXml(m_arguments.ToDictionary(o => o.Name, o => o.Value));
            }
        }

        public AzureTableManagment(string accountName, string key)
            : base(accountName, key) {
        }
        public AzureTableManagment(string accountName, byte[] key)
            : base(accountName, key) {
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return InvokeWithContext(this, arguments);
        }

        internal HttpWebResponse InvokeWithContext(object context, params object[] arguments) {
            object body;
            var request = ConstructRequest(context, arguments, out body);

            if (body != null) {
                var text = (TextDeclaration)body;
                var requestStream = request.GetRequestStream();
                text.ToTextEncoder().CopyTo(requestStream);
            }

            AddAuthroizationHeader(request, tableScheme: true);
            return (HttpWebResponse)request.GetResponse();
        }

        [RestHost]
        public new string AcccountName {
            get { return base.AcccountName; }
        }

        [RestGet, RestQuery("restype", "service"), RestQuery("comp", "properties")]
        public HttpWebResponse Properties() {
            return Invoke();
        }

        [RestTemplate(typeof(AzureServicePropertiesTemplate))]
        [RestPut, RestQuery("restype", "service"), RestQuery("comp", "properties")]
        public HttpWebResponse Update(
            AzureTableLogging logging = null,
            AzureTableMetrics metrics = null) {

            return Invoke(logging, metrics);
        }

        [RestPost, RestPath("tables")]
        public HttpWebResponse CreateTable(
            [RestContent("{0:ToUpperFirst}")]string tableName) {
            return Invoke(tableName);
        }

        [RestGet, RestPath("tables")]
        public HttpWebResponse Tables() {
            return Invoke();
        }

        public AzureTable GetTable(string name) {
            return new AzureTable(this, name);
        }
    }
    [RestTemplate(typeof(Template))]
    public sealed class AzureTable {

        private class Template : TextDeclaration {

            [RestHeader]
            internal const string ContentType = "application/atom+xml";

            private IEnumerable<RestArgument> m_arguments;

            internal Template(IEnumerable<RestArgument> arguments) {
                m_arguments = arguments;
            }

            protected override IEnumerable Children(DeclarationContext frame) {
                var partitionKey = m_arguments.First().Value;
                var rowKey = m_arguments.Second().Value;
                var cells = (IEnumerable<AzureCell>)m_arguments.Last().Value;

                var dictionary = new Dictionary<string, object>();
                dictionary.AddRange(cells, o => o.Name, o => o.Value);
                dictionary["PartitionKey"] = partitionKey;
                dictionary["RowKey"] = rowKey;

                return new AtomXml(dictionary);
            }
        }

        private AzureTableManagment m_credentials;
        private string m_name;

        internal AzureTable(AzureTableManagment credentials, string name) {
            m_credentials = credentials;
            m_name = name;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return m_credentials.InvokeWithContext(this, arguments);
        }

        [RestParent]
        public AzureTableManagment Credentials {
            get { return m_credentials; }
        }

        public string Name {
            get { return m_name; }
        }
        public AzureEntity GetEntity(string partitionKey, string rowKey) {
            return new AzureEntity(this, partitionKey, rowKey);
        }

        [RestDelete, RestPath("tables('{1:<%:Name%>}')")]
        public HttpWebResponse Delete() {
            return Invoke();
        }

        [RestPost, RestPath("{1:<%:Name%>}")]
        public HttpWebResponse Insert(
            [RestContent]string partitionKey,
            [RestContent]string rowKey,
            [RestContent]params AzureCell[] cells) {
            return Invoke(partitionKey, rowKey, cells);
        }

        [RestGet, RestPath("{1:<%:Name%>}()")]
        public HttpWebResponse Query(
            [RestQuery("$filter", "{0}")]string filter,
            [RestQuery("$select", "{0}")]string select,
            [RestQuery("$top", "{0}")]int? top) {
            return Invoke(filter, select, top);
        }
    }
    [RestTemplate(typeof(Template))]
    [RestPath("{1:<%:TableName%>}(PartitionKey='{1:<%:PartitionKey%>}',RowKey='{1:<%:RowKey%>}')")]
    public sealed class AzureEntity {

        private class Template : TextDeclaration {

            [RestHeader]
            internal const string ContentType = "application/atom+xml";

            private IEnumerable<RestArgument> m_arguments;

            internal Template(IEnumerable<RestArgument> arguments) {
                m_arguments = arguments;
            }

            protected override IEnumerable Children(DeclarationContext frame) {
                var dictionary = new Dictionary<string, object>();
                
                var cells = (IEnumerable<AzureCell>)m_arguments.Single().Value;
                dictionary.AddRange(cells, o => o.Name, o => o.Value);

                return new AtomXml(dictionary);
            }
        }

        private AzureTable m_table;
        private string m_partitionKey;
        private string m_rowKey;

        internal AzureEntity(AzureTable table, string partitionKey, string rowKey) {
            m_table = table;
            m_partitionKey = partitionKey;
            m_rowKey = rowKey;
        }

        private HttpWebResponse Invoke(params object[] arguments) {
            return m_table.Credentials.InvokeWithContext(this, arguments);
        }

        private string TableName {
            get { return m_table.Name; }
        }

        [RestParent]
        public AzureTable Table {
            get { return m_table; }
        }
        public string PartitionKey {
            get { return m_partitionKey; }
        }
        public string RowKey {
            get { return m_rowKey; }
        }

        [RestPut]
        public HttpWebResponse Update(
            AzureIfUnchanged ifUnchanged = null,
            [RestContent]params AzureCell[] cells) {
            return Invoke(ifUnchanged, cells);
        }

        [RestMerge]
        public HttpWebResponse Merge(
            AzureIfUnchanged ifUnchanged = null,
            [RestContent]params AzureCell[] cells) {
            return Invoke(ifUnchanged, cells);
        }

        [RestDelete]
        public HttpWebResponse Delete(
            AzureIfUnchanged ifUnchanged = null) {
            return Invoke(ifUnchanged ?? new AzureIfUnchanged());
        }
    }
    public sealed class AzureCell {
        private string m_name;
        private object m_value;

        public AzureCell(string name, object value) {
            m_name = name;
            m_value = value;
        }

        public string Name {
            get { return m_name; }
        }
        public object Value {
            get { return m_value; }
        }
    }
    public sealed class AzureTableLogging : AzureLogging {

        public AzureTableLogging(
            int? version = null,
            bool? delete = null,
            bool? read = null,
            bool? write = null,
            bool? retentionEnable = null,
            int? retentionDays = null)
            : base(version, delete, read, write,
                retentionEnable, retentionDays) {
        }
    }
    public sealed class AzureTableMetrics : AzureMetrics {

        public AzureTableMetrics(
            int? version = null,
            bool? enable = null,
            bool? includeApi = null,
            bool? retentionEnable = null,
            int? retentionDays = null)
            : base(version, enable, includeApi,
                retentionEnable, retentionDays) {
        }
    }
}
